//Copyright (c) Microsoft Corporation.  All rights reserved.

using Eas.WebCrawler.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using Eas.WebCrawler.Model.Actions;

namespace Eas.WebCrawler.Model.Core {
    internal class BaseProcessingState : Eas.WebCrawler.Common.BaseDisposable, Eas.WebCrawler.Interfaces.ProcessingState {
        internal const int ResponseBufferSize = 5000;
        public static BaseProcessingState EmptyState;
        static BaseProcessingState() {
             EmptyState = new BaseProcessingState(null, 0);
             //This thing will not hold anything that needs disposing, and by not disposing of it my diagnostics are hosed.
             EmptyState.Dispose();
        }

        private Eas.WebCrawler.Interfaces.Request _Request;
        private WebRequestInterface _WebRequest;
        private WebResponseInterface _WebResponse;
        private System.IO.Stream _ResponseStream;
        private bool _IsTextResponse;
        private int _ID;
        private bool _WasPaused;
        private ProcessingAction _Action;
        private WorkerContext _WorkerContext;
        private ChangeQueue _ChangeQueue;
        private ProcessingAction _PausedAction;
        private IAsyncResult _PausedResult;
        private int _RedirectDepth;
        private Eas.WebCrawler.Common.Threading.SpinWaitLock _TerminationLock = new Eas.WebCrawler.Common.Threading.SpinWaitLock();
        private bool _StreamReadHasCompleted;

        private Eas.WebCrawler.Interfaces.TemporaryStorage _ResponseStore;
        private Eas.WebCrawler.Model.StorageWriters.StorageWriter _StorageWriter;
        byte[] _Buffer = new byte[ResponseBufferSize];

        static Eas.WebCrawler.Common.Diagnostics.DeliTicketDispenser _DeliTicketDispenser = new Eas.WebCrawler.Common.Diagnostics.DeliTicketDispenser();

        public BaseProcessingState(WorkerContext workerContext, int id) {
            _ID = id;
            _WorkerContext = workerContext;
            _ChangeQueue = new ChangeQueue();
            this.ClearRequestData();
            _Action = Eas.WebCrawler.Model.Actions.UnstartedAction.Instance;
        }

        public void SetTerminated() {
            _TerminationLock.Enter();
            try {
            }
            finally { _TerminationLock.Exit(); }
        }

        public TemporaryStorage ResponseStore {
            get { return _ResponseStore; }
            set { _ResponseStore = value; }
        }

        internal StorageWriters.StorageWriter StorageWriter {
            get { return _StorageWriter; }
            set { _StorageWriter = value; }
        }

        internal byte[] ResponseBuffer {
            get { return _Buffer; }
        }

        internal ProcessingAction Action {
            get { return _Action; }
        }

        internal int RedirectDepth {
            get { return _RedirectDepth; }
            set { _RedirectDepth = value; }
        }

        #region ProcessingState Members

        public Eas.WebCrawler.Interfaces.WorkerState WorkerState {
            get { return _Action.WorkerState; }
        }

        public Eas.WebCrawler.Interfaces.Request Request {
            get { return _Request; }
            set { _Request = value; }
        }

        public WebRequestInterface WebRequest {
            get { return _WebRequest; }
            set { _WebRequest = value; }
        }

        public WebResponseInterface WebResponse {
            get { return _WebResponse; }
            set { _WebResponse = value; }
        }

        public System.IO.Stream ResponseStream {
            get { return _ResponseStream; }
            set { _ResponseStream = value; } 
        }

        public bool IsTextResponse {
            get { return _IsTextResponse; }
            set { _IsTextResponse = value; }
        }

        public int Id {
            get { return _ID; }
        }

        public bool WasPaused {
            get { return _WasPaused  ; }
        }

        internal ProcessingAction PausedAction {
            get { return _PausedAction; }
            set { _PausedAction = value; }
        }

        internal IAsyncResult PausedResult {
            get { return _PausedResult; }
            set { _PausedResult = value; }
        }


        public void RequestChange(WorkerState newState) {
            _ChangeQueue.EnQueueRequest(newState);
        }

        public void CopyChangeRequests(ProcessingState state) {
            Eas.WebCrawler.Interfaces.WorkerState? change = _ChangeQueue.DeQueueRequest();
            while (change.HasValue) {
                state.RequestChange(change.Value);
                change = DeQueueChangeRequest();
            }
        }

        public void SetPaused() { _WasPaused = true; }
        
        public void Reset() {
            ClearRequestData();
        }

        internal bool StreamReadHasCompleted {
            get { return _StreamReadHasCompleted; }
            set { _StreamReadHasCompleted = value; }
        }

        #endregion


        #region IDisposable Members
        protected override void Dispose(bool isDisposing){
            try {
                if (isDisposing) {
                    ClearRequestData();
                }
            }
            finally{
                base.Dispose(isDisposing);
            }
        }
        #endregion

        internal WorkerState? DeQueueChangeRequest() {
            return _ChangeQueue.DeQueueRequest();
        }

        private void ClearRequestData() {

            try {
                if (_ResponseStream != null)
                    _ResponseStream.Close();
            }
            catch (System.Exception) { }

            try {
                if (_WebResponse != null)
                    _WebResponse.Close();
            }
            catch (System.Exception) { }

            try {
                if (_StorageWriter != null)
                    _StorageWriter.Dispose();
            }
            catch (Exception) { }

            try {
                if (_ResponseStore != null)
                    _ResponseStore.Dispose();
            }
            catch (System.Exception) { }

            _StorageWriter = null;
            _ResponseStore = null;
            _ResponseStream = null;
            _WebResponse = null;
            _WebRequest = null;

            _StreamReadHasCompleted = false;
            _WasPaused = false;
            _IsTextResponse = true;
            _Request = null;
            _RedirectDepth = 0;
            _PausedAction = null;
            _PausedResult = null;
            
        }

        public void AbortRequest() {
            _TerminationLock.Enter();
            try {
                try {
                    if (_WebRequest != null)
                        _WebRequest.Abort();
                }
                catch (System.Exception) { }
            }
            finally { _TerminationLock.Exit(); }
        }

        public static void TransitionOrCancel(object state, bool wasTimeout) {
            IAsyncResult result = (IAsyncResult)state;
            PerformTransition(result);
        }

        public static void PerformTransition(IAsyncResult result) {

            //get deli ticket for trace purposes
            Eas.WebCrawler.Common.Diagnostics.ReferenceTypeWrapper<long> transitionID = _DeliTicketDispenser.GetNextAsWrapper();

            //get state
            BaseProcessingState state = (BaseProcessingState)result.AsyncState;
            WorkerContext context = state._WorkerContext;

            try {            
                context.EventReporter.ReportTrace(TraceLevel.HighImpact, state, "<{0}>\tStarting Transition.", transitionID);

                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "<{0}>\tLoading change requests into state.", transitionID);
                //get new change requests
                context.StateTracker.LoadChangeRequestsIntoState(state);
                
                //if timeout, then call abort()
                if (!result.IsCompleted) {
                    context.EventReporter.ReportTrace(TraceLevel.HighImpact, state, "<{0}>\tTimeout occurred, canceling network operation.", transitionID);
                    try { state.AbortRequest(); }
                    catch (Exception) { }
                }

                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "<{0}>Calling TransitionToNextState.", transitionID);
                //Transition, calling changerequest logic and call EndAction to get new action
                ActionId oldActionID = state.Action.ActionId;
                TransitionToNextState(state, context, result);
                context.EventReporter.ReportTrace(TraceLevel.Detailed, state, "<{0}>Transitioned from {1} to {2}", transitionID, oldActionID, state.Action.ActionId);
                context.StateTracker.TraceStateCounts();
                BeginNextAction(state, context);
                return;

            }
            catch (Eas.WebCrawler.Model.Core.WebFailureException exception) {
                context.EventReporter.ReportWebResponseFailure(state, exception.ResponseCode, exception.Message);
                ErrorTransition(state, context);                
            }
            catch (System.Exception exception) {
                context.EventReporter.ReportError(state, string.Empty, exception);
                ErrorTransition(state, context);
            }
        }

        internal static void BeginNextAction(BaseProcessingState state, WorkerContext context) {
            //call Begin Action and get IAsyncResult
            IAsyncResult result = state.Action.BeginAction(context, state, null);

            if (result != null){        //If null, then end
                //register callback
                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Registering callback");
                System.Threading.ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new System.Threading.WaitOrTimerCallback(TransitionOrCancel), result, context.WebCrawlerContext.TimeoutInMilliseconds, true);
            }
        }

        private static void ErrorTransition(BaseProcessingState state, WorkerContext context) {
            if (state.WasPaused)
                context.WebCrawlerContext.Store.AddPriorityRequest(state.Request);
            state.Reset();
            ProcessingAction nextAction = context.ActionFactory.CreateGetRequestAction(state, context);
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Calling TransitionToNextState.");
            state.SetAction(nextAction);
            BeginNextAction(state, context);
        }

        internal static void TransitionToNextState(BaseProcessingState state, WorkerContext context, IAsyncResult result) {
            System.Diagnostics.Debug.Assert(state.WorkerState != WorkerState.Stopped);
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Getting change requests.");
            WorkerState? changeRequest = state.DeQueueChangeRequest();
            if ( (!changeRequest.HasValue) || changeRequest.Value==state.WorkerState) {
                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "No change requests, calling EndAction.");
                ProcessingAction nextAction = state.Action.EndAction(context, state, result);
                state.SetAction(nextAction);
            }
            else {
                switch (changeRequest.Value) {
                    case WorkerState.Paused:
                        context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Pause Change Requested, transitioning...");
                        TransitionToPaused(state, context, result);
                        break;
                    case WorkerState.Stopped:
                        context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Stop Change Requested, transitioning...");
                        TransitionToStopped(state, context, result);
                        break;
                    default:  //WorkerState.Working
                        context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Working Change Requested, transitioning...");
                        System.Diagnostics.Debug.Assert(changeRequest.Value == WorkerState.Working);
                        TransitionToWorking(state, context, result);
                        break;
                }
            }
        }

        private static void TransitionToWorking(BaseProcessingState state, WorkerContext context, IAsyncResult result) {
            switch (state.WorkerState) {
                case WorkerState.Unstarted: {
                        context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Transitioning from Unstarted to started.");
                        context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Performing Competing Action.");    
                        state.Action.PerformCompletingAction(context, state, result);
                        ProcessingAction nextAction = context.ActionFactory.CreateGetRequestAction(state, context);
                        state.SetAction(nextAction);
                        break;
                    }
                default: {  //Paused
                        context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Transitioning from paused to working.");
                        System.Diagnostics.Debug.Assert(state.WorkerState == WorkerState.Paused);
                        TransitionFromPaused(state, context, result);
                        break;
                    }
            }
        }

        private static void TransitionToStopped(BaseProcessingState state, WorkerContext context, IAsyncResult result) {
            switch (state.WorkerState) {
                case WorkerState.Paused:
                    context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "We were paused, transitioning from pause to stop.");
                    TransitionFromPausedToStop(state, context, result);
                    break;
                default: //Stopped, Unstarted, Working.
                    context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Performing completing action before stop.");
                    state.Action.PerformCompletingAction(context, state, result);
                    ProcessingAction nextAction = context.ActionFactory.CreateTerminalAction(state, context);
                    state.SetAction(nextAction);
                    break;
            }
        }

        private static void TransitionFromPaused(BaseProcessingState state, WorkerContext context, IAsyncResult result) {
            //Transition from Current (Pause) action to _PausedAction.
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Performing completing action on pause action..");
            state.Action.PerformCompletingAction(context, state, result);
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Transitioning from pause to previously paused action.");
            state.SetAction(state._PausedAction);
            state._PausedAction = null;

            //Transition from _Paused Action to nextAction.
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Calling end action on previously paused action to get next action.");
            ProcessingAction nextAction = state.Action.EndAction(context, state, state._PausedResult);
            state._PausedResult = null;
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Transitioning to next action.");
            state.SetAction(nextAction);
        }

        private static void TransitionFromPausedToStop(BaseProcessingState state, WorkerContext context, IAsyncResult result) {
            //Transition from Current (Pause) action to _PausedAction.
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Calling perform completing action on paused state.");
            state.Action.PerformCompletingAction(context, state, result);
            state.SetAction(state._PausedAction);
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Transitioning from pause to previously paused action.");
            state._PausedAction = null;

            //Now TransitionToStopped
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Transitioning from previously paused state to stopped state.");
            TransitionToStopped(state, context, state._PausedResult);
            state._PausedResult = null;
        }

        private static void TransitionToPaused(BaseProcessingState state, WorkerContext context, IAsyncResult result) {
            state.SetPaused();
            state._PausedAction = state._Action;
            state._PausedResult = result;
            state.SetAction(context.ActionFactory.CreatePauseAction(state, context));
        }

        public void SetAction(ProcessingAction nextAction) {
            this._WorkerContext.EventReporter.ReportTrace(TraceLevel.Annoying, this, "Setting next action to {0}", nextAction.ActionId);
            if (nextAction.WorkerState == WorkerState.Paused && _Action.WorkerState!=WorkerState.Paused) {
                PerformTransitionToPause(nextAction);
            }
            else if (_Action.WorkerState == WorkerState.Paused && nextAction.WorkerState!=WorkerState.Paused) {
                PerformTransitionFromPause(nextAction);
            }
            else {
                PerformTransitionWithoutPause(nextAction);
            }
        }

        private void PerformTransitionWithoutPause(ProcessingAction nextAction) {
            PerformSimpleTransition(nextAction);
        }

        private void PerformTransitionToPause(ProcessingAction pauseAction) {
            System.Diagnostics.Debug.Assert(pauseAction.WorkerState == WorkerState.Paused);
            System.Diagnostics.Debug.Assert(this.Action.WorkerState != WorkerState.Paused);
            PerformTransitionWithoutPause(pauseAction);
        
        }

        private void PerformTransitionFromPause(ProcessingAction nextAction) {
            System.Diagnostics.Debug.Assert(this.Action.WorkerState == WorkerState.Paused);
            System.Diagnostics.Debug.Assert(nextAction.WorkerState != WorkerState.Paused);
            PerformTransitionWithoutPause(nextAction);

        }

        private void PerformSimpleTransition(ProcessingAction nextAction) {
            if(!object.ReferenceEquals(this._Action, nextAction)){
                System.Diagnostics.Debug.Assert(_Action.WorkerState == WorkerState.Working || _Action.WorkerState != nextAction.WorkerState);
                ProcessingAction oldAction = this.Action;
                this._Action = nextAction;
                if(oldAction.ActionId!=this._Action.ActionId)
                    _WorkerContext.StateTracker.ReportStateChange(this, oldAction, this._Action);
            }
        }
    }
}
